﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace SpLocalizer.Common.DataStructures.Queues
{
    public class BoundedBuffer<T> : IDisposable
    {
        private readonly int _maxBufferSize;
        private readonly Queue<T> _queue = new Queue<T>();
        protected int _consumersWaiting;
        protected bool _isDisposed;
        protected int _producersWaiting;

        /// <summary>
        /// maxBufferSize should be greater 1
        /// </summary>
        /// <param name="maxBufferSize"></param>
        public BoundedBuffer(int maxBufferSize)
        {
            if (maxBufferSize < 1)
                throw new ArgumentException(maxBufferSize.ToString());
            _maxBufferSize = maxBufferSize;
        }

        public bool IsEmpty
        {
            get
            {
                lock (_queue)
                {
                    return _queue.Count == 0;
                }
            }
        }

        public int Count
        {
            get
            {
                lock (_queue)
                {
                    return _queue.Count;
                }
            }
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            lock (_queue)
            {
                if (!_isDisposed)
                {
                    GC.SuppressFinalize(this);
                    _isDisposed = true;
                    Monitor.PulseAll(_queue);
                }
            }
        }

        #endregion

        protected virtual void BufferOverFlowAction()
        {
            _producersWaiting++;
            Monitor.Wait(_queue);
            _producersWaiting--;
        }

        public void Enqueue(T queueValue)
        {
            lock (_queue)
            {
                while (_queue.Count == (_maxBufferSize - 1) && !_isDisposed)
                {
                    BufferOverFlowAction();
                }
                _queue.Enqueue(queueValue);
                if (_consumersWaiting > 0)
                    Monitor.PulseAll(_queue);
            }
        }

        public T Dequeue()
        {
            T queueValue;
            lock (_queue)
            {
                while (_queue.Count == 0)
                {
                    _consumersWaiting++;
                    Monitor.Wait(_queue);
                    _consumersWaiting--;
                }
                queueValue = _queue.Dequeue();
                if (_producersWaiting > 0)
                    Monitor.PulseAll(_queue);
            }
            return queueValue;
        }

        ~BoundedBuffer()
        {
            lock (_queue)
            {
                if (!_isDisposed)
                {
                    _isDisposed = true;
                    Monitor.PulseAll(_queue);
                }
            }
        }
    }
}